/*
 * Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
 * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
 */

package org.jetbrains.kotlin.analysis.api.fir.components

import org.jetbrains.kotlin.analysis.api.components.KaReturnValueStatus
import org.jetbrains.kotlin.analysis.api.components.containingDeclaration
import org.jetbrains.kotlin.analysis.api.fir.KaFirSession
import org.jetbrains.kotlin.analysis.api.fir.symbols.KaFirNamedClassSymbolBase
import org.jetbrains.kotlin.analysis.api.fir.symbols.KaFirPackageSymbol
import org.jetbrains.kotlin.analysis.api.fir.symbols.KaFirPsiJavaClassSymbol
import org.jetbrains.kotlin.analysis.api.fir.symbols.KaFirSymbol
import org.jetbrains.kotlin.analysis.api.fir.utils.firSymbol
import org.jetbrains.kotlin.analysis.api.fir.utils.withSymbolAttachment
import org.jetbrains.kotlin.analysis.api.impl.base.components.KaBaseSymbolInformationProvider
import org.jetbrains.kotlin.analysis.api.lifetime.withValidityAssertion
import org.jetbrains.kotlin.analysis.api.symbols.*
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget
import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget
import org.jetbrains.kotlin.fir.analysis.checkers.getAllowedAnnotationTargets
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.resolve.calls.FirSimpleSyntheticPropertySymbol
import org.jetbrains.kotlin.fir.resolve.calls.noJavaOrigin
import org.jetbrains.kotlin.fir.symbols.impl.FirBackingFieldSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.deprecation.DeprecationInfo
import org.jetbrains.kotlin.resolve.deprecation.DeprecationLevelValue
import org.jetbrains.kotlin.resolve.deprecation.SimpleDeprecationInfo
import org.jetbrains.kotlin.resolve.ReturnValueStatus
import org.jetbrains.kotlin.utils.exceptions.errorWithAttachment

internal class KaFirSymbolInformationProvider(
    override val analysisSessionProvider: () -> KaFirSession,
) : KaBaseSymbolInformationProvider<KaFirSession>(), KaFirSessionComponent {
    private companion object {
        private val OBSOLETE_SYMBOL_DEPRECATION_INFO = SimpleDeprecationInfo(
            deprecationLevel = DeprecationLevelValue.HIDDEN,
            propagatesToOverrides = true,
            message = null,
        )
    }

    override val KaSymbol.deprecationStatus: DeprecationInfo?
        get() = withValidityAssertion {
            if (this is KaFirPackageSymbol || this is KaReceiverParameterSymbol) return null
            require(this is KaFirSymbol<*>) { "${this::class}" }

            // Optimization: Avoid building `firSymbol` of `KtFirPsiJavaClassSymbol` if it definitely isn't deprecated.
            if (this is KaFirPsiJavaClassSymbol && !mayHaveDeprecation()) {
                return null
            }

            val firSymbol = this.firSymbol

            // A symbol exists for compatibility reasons and should never be referenced in user code
            val isObsoleteSymbol = firSymbol.origin == FirDeclarationOrigin.Synthetic.FakeHiddenInPreparationForNewJdk
                    || (firSymbol is FirSimpleSyntheticPropertySymbol && firSymbol.noJavaOrigin)

            if (isObsoleteSymbol) {
                return OBSOLETE_SYMBOL_DEPRECATION_INFO
            }

            return when (firSymbol) {
                is FirPropertySymbol -> firSymbol.getDeprecationForCallSite(analysisSession.firSession, AnnotationUseSiteTarget.PROPERTY)
                is FirBackingFieldSymbol -> firSymbol.getDeprecationForCallSite(analysisSession.firSession, AnnotationUseSiteTarget.FIELD)
                else -> firSymbol.getDeprecationForCallSite(analysisSession.firSession)
            }?.toDeprecationInfo()
        }

    override val KaNamedFunctionSymbol.canBeOperator: Boolean
        get() = withValidityAssertion {
            val functionFir = this@canBeOperator.firSymbol.fir as? FirNamedFunction ?: return false
            return OperatorFunctionChecks.isOperator(
                functionFir,
                analysisSession.firSession,
                analysisSession.getScopeSessionFor(analysisSession.firSession)
            ).isSuccess
        }


    private fun KaFirPsiJavaClassSymbol.mayHaveDeprecation(): Boolean {
        if (!hasAnnotations) return false

        // Check the simple names of the Java annotations. While presence of such an annotation name does not prove deprecation, it is a
        // necessary condition for it. Type aliases are not a problem here: Java code cannot access Kotlin type aliases. (Currently,
        // deprecation annotation type aliases do not work in Kotlin, either, but this might change in the future.)
        val deprecationAnnotationSimpleNames = analysisSession.firSession.annotationPlatformSupport.deprecationAnnotationsSimpleNames
        return annotationSimpleNames.any { it != null && it in deprecationAnnotationSimpleNames }
    }

    override fun KaSymbol.deprecationStatus(annotationUseSiteTarget: AnnotationUseSiteTarget?): DeprecationInfo? = withValidityAssertion {
        if (this is KaReceiverParameterSymbol) return null

        require(this is KaFirSymbol<*>)
        return if (annotationUseSiteTarget != null) {
            firSymbol.getDeprecationForCallSite(analysisSession.firSession, annotationUseSiteTarget)
        } else {
            firSymbol.getDeprecationForCallSite(analysisSession.firSession)
        }?.toDeprecationInfo()
    }

    override val KaPropertySymbol.getterDeprecationStatus: DeprecationInfo?
        get() = withValidityAssertion {
            require(this is KaFirSymbol<*>)
            return firSymbol.getDeprecationForCallSite(
                analysisSession.firSession,
                AnnotationUseSiteTarget.PROPERTY_GETTER,
                AnnotationUseSiteTarget.PROPERTY,
            )?.toDeprecationInfo()
        }

    override val KaPropertySymbol.setterDeprecationStatus: DeprecationInfo?
        get() = withValidityAssertion {
            require(this is KaFirSymbol<*>)
            return firSymbol.getDeprecationForCallSite(
                analysisSession.firSession,
                AnnotationUseSiteTarget.PROPERTY_SETTER,
                AnnotationUseSiteTarget.PROPERTY,
            )?.toDeprecationInfo()
        }

    private fun FirDeprecationInfo.toDeprecationInfo(): DeprecationInfo {
        // We pass null as the message, otherwise we can trigger a contract violation
        // as getMessage will call lazyResolveToPhase(ANNOTATION_ARGUMENTS)
        // TODO(KT-67823) stop exposing compiler internals, as the message isn't actually required by the callers.
        return SimpleDeprecationInfo(deprecationLevel, propagatesToOverrides, null)
    }

    override val KaClassSymbol.annotationApplicableTargets: Set<KotlinTarget>?
        get() = withValidityAssertion {
            if (this !is KaFirNamedClassSymbolBase<*>) return null
            if (firSymbol.classKind != ClassKind.ANNOTATION_CLASS) return null
            return firSymbol.getAllowedAnnotationTargets(analysisSession.firSession)
        }

    override val KaSymbol.importableFqName: FqName?
        get() = withValidityAssertion {
            when (this) {
                is KaClassLikeSymbol -> classId?.asSingleFqName()
                is KaConstructorSymbol -> containingClassId?.takeIf {
                    context(analysisSession) {
                        when (val containingDeclaration = containingDeclaration) {
                            is KaNamedClassSymbol -> !containingDeclaration.isInner
                            is KaTypeAliasSymbol -> true
                            else -> false
                        }
                    }
                }?.asSingleFqName()

                is KaCallableSymbol -> {
                    val callableId = callableId ?: return null
                    if (callableId.classId == null) {
                        // no containing class -> top level callable
                        return callableId.asSingleFqName()
                    }

                    val containingClass = context(analysisSession) { containingDeclaration as? KaNamedClassSymbol } ?: return null
                    val canBeImported = when (containingClass.classKind) {
                        KaClassKind.CLASS, KaClassKind.ENUM_CLASS, KaClassKind.INTERFACE, KaClassKind.ANNOTATION_CLASS -> when (this) {
                            is KaNamedFunctionSymbol -> isStatic
                            is KaJavaFieldSymbol -> isStatic
                            is KaPropertySymbol -> isStatic
                            is KaEnumEntrySymbol -> true
                            else -> false
                        }

                        KaClassKind.OBJECT, KaClassKind.COMPANION_OBJECT -> true
                        KaClassKind.ANONYMOUS_OBJECT -> errorWithAttachment("Anonymous object is not expected here since it cannot have ClassId") {
                            withSymbolAttachment("symbol", analysisSession, this@importableFqName)
                            withSymbolAttachment("containingClass", analysisSession, containingClass)
                        }
                    }

                    if (canBeImported) callableId.asSingleFqName() else null
                }

                else -> null
            }
        }

    override val KaNamedFunctionSymbol.returnValueStatus: KaReturnValueStatus
        get() = withValidityAssertion {
            when (firSymbol.resolvedStatus.returnValueStatus) {
                ReturnValueStatus.MustUse -> KaReturnValueStatus.MustUse
                ReturnValueStatus.ExplicitlyIgnorable -> KaReturnValueStatus.ExplicitlyIgnorable
                ReturnValueStatus.Unspecified -> KaReturnValueStatus.Unspecified
            }
        }
}
